// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
    internal abstract class AndXServerMessageBlock : ServerMessageBlock
    {
        private const int AndxCommandOffset = 1;

        private const int AndxReservedOffset = 2;

        private const int AndxOffsetOffset = 3;

        private byte _andxCommand = unchecked(unchecked(0xFF));

        private int _andxOffset;

        internal ServerMessageBlock Andx;

        public AndXServerMessageBlock()
        {
        }

        internal AndXServerMessageBlock(ServerMessageBlock andx)
        {
            if (andx != null)
            {
                this.Andx = andx;
                _andxCommand = andx.Command;
            }
        }

        internal virtual int GetBatchLimit(byte command)
        {
            return 0;
        }

        internal override int Encode(byte[] dst, int dstIndex)
        {
            int start = HeaderStart = dstIndex;
            dstIndex += WriteHeaderWireFormat(dst, dstIndex);
            dstIndex += WriteAndXWireFormat(dst, dstIndex);
            Length = dstIndex - start;
            if (Digest != null)
            {
                Digest.Sign(dst, HeaderStart, Length, this, Response);
            }
            return Length;
        }

        internal override int Decode(byte[] buffer, int bufferIndex)
        {
            int start = HeaderStart = bufferIndex;
            bufferIndex += ReadHeaderWireFormat(buffer, bufferIndex);
            bufferIndex += ReadAndXWireFormat(buffer, bufferIndex);
            Length = bufferIndex - start;
            return Length;
        }

        internal virtual int WriteAndXWireFormat(byte[] dst, int dstIndex)
        {
            int start = dstIndex;
            WordCount = WriteParameterWordsWireFormat(dst, start + AndxOffsetOffset + 2);
            WordCount += 4;
            // for command, reserved, and offset
            dstIndex += WordCount + 1;
            WordCount /= 2;
            dst[start] = unchecked((byte)(WordCount & unchecked(0xFF)));
            ByteCount = WriteBytesWireFormat(dst, dstIndex + 2);
            dst[dstIndex++] = unchecked((byte)(ByteCount & unchecked(0xFF)));
            dst[dstIndex++] = unchecked((byte)((ByteCount >> 8) & unchecked(0xFF)));
            dstIndex += ByteCount;
            if (Andx == null
                || SmbConstants.UseBatching == false
                || BatchLevel >= GetBatchLimit(Andx.Command))
            {
                _andxCommand = unchecked(unchecked(0xFF));
                Andx = null;
                dst[start + AndxCommandOffset] = unchecked(unchecked(0xFF));
                dst[start + AndxReservedOffset] = unchecked(unchecked(0x00));
                //            dst[start + ANDX_OFFSET_OFFSET] = (byte)0x00;
                //            dst[start + ANDX_OFFSET_OFFSET + 1] = (byte)0x00;
                dst[start + AndxOffsetOffset] = unchecked(unchecked(0xde));
                dst[start + AndxOffsetOffset + 1] = unchecked(unchecked(0xde));
                // andx not used; return
                return dstIndex - start;
            }
            Andx.BatchLevel = BatchLevel + 1;
            dst[start + AndxCommandOffset] = _andxCommand;
            dst[start + AndxReservedOffset] = unchecked(unchecked(0x00));
            _andxOffset = dstIndex - HeaderStart;
            WriteInt2(_andxOffset, dst, start + AndxOffsetOffset);
            Andx.UseUnicode = UseUnicode;
            if (Andx is AndXServerMessageBlock)
            {
                Andx.Uid = Uid;
                dstIndex += ((AndXServerMessageBlock)Andx).WriteAndXWireFormat(dst, dstIndex);
            }
            else
            {
                // the andx smb is not of type andx so lets just write it here and
                // were done.
                int andxStart = dstIndex;
                Andx.WordCount = Andx.WriteParameterWordsWireFormat(dst, dstIndex);
                dstIndex += Andx.WordCount + 1;
                Andx.WordCount /= 2;
                dst[andxStart] = unchecked((byte)(Andx.WordCount & unchecked(0xFF)));
                Andx.ByteCount = Andx.WriteBytesWireFormat(dst, dstIndex + 2);
                dst[dstIndex++] = unchecked((byte)(Andx.ByteCount & unchecked(0xFF)));
                dst[dstIndex++] = unchecked((byte)((Andx.ByteCount >> 8) & unchecked(0xFF)));
                dstIndex += Andx.ByteCount;
            }
            return dstIndex - start;
        }

        internal virtual int ReadAndXWireFormat(byte[] buffer, int bufferIndex)
        {
            int start = bufferIndex;
            WordCount = buffer[bufferIndex++];
            if (WordCount != 0)
            {
                _andxCommand = buffer[bufferIndex];
                _andxOffset = ReadInt2(buffer, bufferIndex + 2);
                if (_andxOffset == 0)
                {
                    _andxCommand = unchecked(unchecked(0xFF));
                }
                if (WordCount > 2)
                {
                    ReadParameterWordsWireFormat(buffer, bufferIndex + 4);
                    if (Command == SmbComNtCreateAndx && ((SmbComNtCreateAndXResponse)this).IsExtended)
                    {
                        WordCount += 8;
                    }
                }
                bufferIndex = start + 1 + (WordCount * 2);
            }
            ByteCount = ReadInt2(buffer, bufferIndex);
            bufferIndex += 2;
            if (ByteCount != 0)
            {
                int n;
                n = ReadBytesWireFormat(buffer, bufferIndex);
                bufferIndex += ByteCount;
            }
            if (ErrorCode != 0 || _andxCommand == unchecked(unchecked(0xFF)))
            {
                _andxCommand = unchecked(unchecked(0xFF));
                Andx = null;
            }
            else
            {
                if (Andx == null)
                {
                    _andxCommand = unchecked(unchecked(0xFF));
                    throw new RuntimeException("no andx command supplied with response");
                }
                bufferIndex = HeaderStart + _andxOffset;
                Andx.HeaderStart = HeaderStart;
                Andx.Command = _andxCommand;
                Andx.ErrorCode = ErrorCode;
                Andx.Flags = Flags;
                Andx.Flags2 = Flags2;
                Andx.Tid = Tid;
                Andx.Pid = Pid;
                Andx.Uid = Uid;
                Andx.Mid = Mid;
                Andx.UseUnicode = UseUnicode;
                if (Andx is AndXServerMessageBlock)
                {
                    bufferIndex += ((AndXServerMessageBlock)Andx).ReadAndXWireFormat(buffer,
                                                                                     bufferIndex);
                }
                else
                {
                    buffer[bufferIndex++] = unchecked((byte)(Andx.WordCount & unchecked(0xFF)));
                    if (Andx.WordCount != 0)
                    {
                        if (Andx.WordCount > 2)
                        {
                            bufferIndex += Andx.ReadParameterWordsWireFormat(buffer, bufferIndex);
                        }
                    }
                    Andx.ByteCount = ReadInt2(buffer, bufferIndex);
                    bufferIndex += 2;
                    if (Andx.ByteCount != 0)
                    {
                        Andx.ReadBytesWireFormat(buffer, bufferIndex);
                        bufferIndex += Andx.ByteCount;
                    }
                }
                Andx.Received = true;
            }
            return bufferIndex - start;
        }

        public override string ToString()
        {
            return base.ToString()
                    + ",andxCommand=0x" + Hexdump.ToHexString(_andxCommand, 2)
                    + ",andxOffset=" + _andxOffset;
        }
    }
}
